Skip to main content

--

Disable Remote DB Access

Overview

Disabling remote database access means configuring your database and network so the DB service cannot be reached from untrusted networks (especially the public internet). In practice, this is achieved by binding the database listener to local or private interfaces, enforcing firewall rules, and restricting authentication so only approved application hosts and administrators can connect.

History

  • Early self-managed deployments often exposed database ports directly for convenience and remote administration.
  • As internet scanning and credential stuffing became common, security practice shifted toward private networking, bastion access, and least-privilege connectivity.
  • Cloud and container platforms further reinforced this pattern by promoting private subnets and security-group-based isolation.

Adoption

This control is commonly used in:

  • Production web applications where the database is a private dependency
  • Regulated environments requiring network segmentation and least privilege
  • Cloud deployments using private subnets/VPCs and security groups
  • Kubernetes and service-mesh environments where database connectivity is limited to specific namespaces/workloads

Maintainer

Maintained by the operating system, database vendors, and infrastructure/community best practices (no single project owns this control).

Best when to use

  • The database does not need direct access from the public internet
  • Only one or a few application services should connect to the database
  • Administrators can reach the database through a bastion host, VPN, or SSH tunnel
  • You want to reduce attack surface and prevent opportunistic scanning attacks

Not suitable when

  • You truly require direct client-to-database connections from many unmanaged networks (rare for production)
  • You cannot provide a secure alternative path for administration (VPN, bastion, private peering)
  • Your application architecture depends on remote DB access without a network boundary (re-architect first)

Compatibility notes

  • Database configuration differs by engine (MySQL/MariaDB, PostgreSQL, MongoDB, etc.).
  • Firewalls differ by OS and distro (ufw, firewalld, raw iptables/nftables).
  • Cloud providers add additional network layers (security groups, NACLs) that should be used in addition to host firewalls.
  • Containers and orchestrators may require different steps (pod networking, sidecars, ingress controllers).
Outage risk

Restricting DB access can break production traffic if application hosts are not allowlisted correctly. Always validate the current listeners and active clients before applying changes, and keep a rollback path.

How it works

Remote access is controlled at three layers:

  1. Database listener binding Which IP addresses and interfaces the DB server listens on (localhost only, private subnet, or all interfaces).

  2. Network filtering Firewalls and cloud network controls limit which sources can reach the DB port.

  3. Database authentication and host-based access control DB-level rules restrict which users/roles can connect from which hosts and networks.

Pre-change checks (read-only)

Identify DB process and listening ports

On Linux, list listeners and the owning process:

sudo ss -lntp

Filter for common DB ports:

sudo ss -lntp | grep -E ':(3306|5432|27017|6379)\b' || true

Check which interfaces the service is bound to:

  • 127.0.0.1 or ::1: local-only (not remotely accessible)
  • Private IP (for example, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16): private network only
  • 0.0.0.0 or ::: all interfaces (remote access possible)

Confirm firewall status

Depending on your distro:

sudo ufw status verbose 2>/dev/null || true
sudo firewall-cmd --state 2>/dev/null || true
sudo nft list ruleset 2>/dev/null | head || true

Confirm cloud/network controls (if applicable)

Use your cloud provider console/CLI to confirm:

  • Security group rules
  • Network ACL rules
  • Subnet routing (public vs private)
  • Load balancer rules (DB ports should not be exposed)
ComponentTargetNotes
----
DB bind addresslocalhost or private interface onlyPrefer private subnet if apps are remote
FirewallAllow DB port only from trusted sourcesDeny everything else by default
DB authenticationLeast privilege accounts, host restrictionsDo not rely on network-only controls
Admin accessVPN/bastion/SSH tunnelAvoid direct public access

Database-specific configuration

MySQL / MariaDB

Bind to localhost (local-only)

  1. Locate config (common paths vary):

    • /etc/mysql/mysql.conf.d/mysqld.cnf
    • /etc/my.cnf
    • /etc/my.cnf.d/*.cnf
  2. Set bind address:

[mysqld]
bind-address = 127.0.0.1
  1. Validate config (read-only) if available:
mysqld --help --verbose 2>/dev/null | head -n 50 || true
  1. Restart service:
sudo systemctl restart mysql 2>/dev/null || sudo systemctl restart mariadb
  1. Verify listener:
sudo ss -lntp | grep -E ':(3306)\b' || true

Restrict remote users

Even if bound to private IPs, ensure users are not broadly allowed:

-- Example pattern: prefer host-specific accounts
-- SHOW users and allowed hosts:
SELECT user, host FROM mysql.user;
Host-based auth nuance

MySQL accounts include a host field. Avoid wildcard hosts (%) for privileged users where possible, and prefer application subnets or specific hosts.

PostgreSQL

PostgreSQL controls remote access via both listening addresses and pg_hba.conf.

Bind to localhost (local-only)

  1. Find active config file paths:
psql -U postgres -c "SHOW config_file;" 2>/dev/null || true
psql -U postgres -c "SHOW hba_file;" 2>/dev/null || true
  1. Set listen_addresses:
listen_addresses = 'localhost'
  1. Reload/restart:
sudo systemctl reload postgresql 2>/dev/null || sudo systemctl restart postgresql
  1. Verify listener:
sudo ss -lntp | grep -E ':(5432)\b' || true

Tighten pg_hba.conf

Use CIDR-restricted rules and avoid broad trust methods.

Example restrictive entries:

# Local connections
local all all peer

# App subnet only (example)
host appdb appuser 10.0.10.0/24 scram-sha-256
Authentication safety

Avoid trust for non-local connections. Prefer scram-sha-256 on modern PostgreSQL deployments.

MongoDB

MongoDB controls remote access primarily via bindIp (and auth settings).

Bind to localhost (local-only)

In mongod.conf:

net:
bindIp: 127.0.0.1
port: 27017

Restart and verify:

sudo systemctl restart mongod
sudo ss -lntp | grep -E ':(27017)\b' || true
Operational risk

If MongoDB is currently serving remote application traffic, binding to localhost will immediately break those clients. Confirm application topology first.

Redis (commonly misconfigured)

Redis is frequently exposed accidentally; lock it down aggressively.

Bind to localhost or private interface

In redis.conf:

bind 127.0.0.1 ::1
protected-mode yes

Restart and verify:

sudo systemctl restart redis 2>/dev/null || sudo systemctl restart redis-server
sudo ss -lntp | grep -E ':(6379)\b' || true
High-risk service exposure

Never expose Redis directly to the public internet. If remote access is required, use private networking and strong authentication controls, and prefer TLS-enabled deployments where supported.

Firewall controls (host-level)

Firewall configuration is OS- and distro-specific. The goal is:

  • Deny DB ports from untrusted networks
  • Allow DB ports only from application hosts or private subnets
  • Keep SSH access intact

UFW (Ubuntu/Debian)

Check status:

sudo ufw status verbose

Allow DB access only from an app subnet (example for PostgreSQL):

sudo ufw allow from 10.0.10.0/24 to any port 5432 proto tcp
sudo ufw deny 5432/tcp

Apply/enable if not enabled:

sudo ufw enable
SSH lockout risk

If you manage the host over SSH, confirm an explicit allow rule for SSH (port 22 or your custom port) before enabling or changing firewall defaults.

firewalld (Fedora/RHEL)

Check status:

sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones

Allow from a source subnet (example for MySQL):

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.10.0/24" port protocol="tcp" port="3306" accept'
sudo firewall-cmd --permanent --remove-service=mysql 2>/dev/null || true
sudo firewall-cmd --reload

Secure alternatives to remote DB access

SSH tunneling (admin access)

From your workstation:

ssh -L 5432:127.0.0.1:5432 user@db-host

Then connect locally:

psql -h 127.0.0.1 -p 5432 -U appuser appdb
Operational pattern

SSH tunneling reduces attack surface because the DB remains local/private, while admins can still reach it through authenticated, logged access.

VPN / bastion host

For production environments, prefer:

  • VPN access into a private subnet, or
  • A bastion host with strict access controls and auditing

Validation checklist

Confirm ports are not exposed

On the DB host:

sudo ss -lntp | grep -E ':(3306|5432|27017|6379)\b' || true

From a machine that should not have access, verify the port is blocked (example):

nc -vz db-host 5432

Confirm application connectivity

From an allowed application host, test DB connectivity using the application’s configured credentials and endpoint.

Troubleshooting

Application suddenly cannot connect

Common causes:

  • DB now bound to localhost but app is remote
  • Firewall denies app subnet or host
  • DB-level rules deny the client network
  • DNS points to a public IP while DB is private-only

Safe checks:

sudo ss -lntp | grep -E ':(3306|5432|27017|6379)\b' || true
sudo tail -n 100 /var/log/syslog 2>/dev/null || true
sudo journalctl -u mysql -n 100 2>/dev/null || true
sudo journalctl -u postgresql -n 100 2>/dev/null || true

DB still reachable remotely after changes

Common causes:

  • Cloud security group still allows the port
  • The service is listening on 0.0.0.0 or a public interface
  • A load balancer or port-forward is exposing the DB

Verify listener bindings:

sudo ss -lntp | grep -E ':(3306|5432|27017|6379)\b' || true

Quick reference

GoalRead-only checkTypical control
-----
See listening DB portssudo ss -lntpBind to localhost/private IP
Confirm firewall statusufw status / firewall-cmd --stateDeny DB ports publicly
MySQL local-onlysudo ss -lntp | grep 3306bind-address = 127.0.0.1
PostgreSQL local-onlysudo ss -lntp | grep 5432listen_addresses = 'localhost'
MongoDB local-onlysudo ss -lntp | grep 27017bindIp: 127.0.0.1
Redis local-onlysudo ss -lntp | grep 6379bind 127.0.0.1 ::1
Safe admin accessN/ASSH tunnel, VPN, bastion